'#####################
'#    Thumper V3.0   #
'#    Arne Groh      #
'#    2013-06-05     #
'# agroh@t-online.de #
'#####################

'Security Bits:
'1111 1111
'1100 1001
'1101 1110(8 Mhz Keramik ext)

$regfile = "m8def.dat"
$crystal = 8e6
$baud = 38400
$hwstack = 64                                               ' default use 32 for the hardware stack
$swstack = 40                                               ' default use 10 for the SW stack
$framesize = 40                                             ' default use 40 for the frame space


Const Maxknocks = 10                                        'maximum number of knocks accepted in a sequence
Const Ontime = 20                                           'milliseconds the magnet is pulling
Const Threshold = 30                                        'if knock is stronger than this, it is a knock
Const Debouncetime = 75                                     'milliseconds to pause after a knock is recognized
Const Tolerance = 16                                        'accept differences of this value when comparing received sequence to heard sequence
Const Wakeup_time = 300                                     'time in seconds when device knocks, if nothing happens
Const Rx_string_length =(maxknocks + 2) * 4
Const Rx_array_size = Maxknocks + 2
Const Codestring_length = 10                                'maximal 9 Pausen pro Buchstabe + 1
Const Easterlength = 255                                    'damit die Adresse (I) noch in ein Byte passt
Const Pausen_faktor = 250


Dim My As Byte
Dim Letter As String * 1
Dim Asci As Byte
Dim Nummer As Byte
Dim Knock_string_length As Byte
Dim I As Byte
Dim J As Byte
Dim Tmp As Byte
Dim Knockval As Word
Dim Knockcount As Byte
Dim Checkright As Byte
Dim Abstol As Word
Dim Tolmax As Word
Dim Tolmin As Word

Dim Waittime As Word
Dim Now As Word
Dim Starttime As Word
Dim Knockdelay As Word
Dim Pause As Word
Dim Secs As Byte                                            'seconds to wait for next knock in a sequence
Dim Secs1 As Byte                                           'seconds to wait for an answer after a broadcast
Dim Secs2 As Byte                                           'seconds to keep a direct connection if nothing happens
Dim Secs3 As Word : Secs3 = Wakeup_time                     'seconds to wait until i knock if nothing happens
Dim Millis As Long                                          'not really millisecs but 128 of them are 1 second. counting continuously up
Dim Msecs As Byte                                           'same as above but falls back to 0 every 128 ticks (1 second)

Dim Rx_sender As Byte
Dim Rx_byte As Byte
Dim Rx_count As Byte
Dim Rx_string As String * Rx_string_length
Dim Rx_array(rx_array_size) As String * 4
Dim Rx_length As Byte

Dim Codestring As String * Codestring_length

Dim Flags As Byte
Rx_received Alias Flags.0 : Reset Rx_received
Printed Alias Flags.1 : Reset Printed
Connected Alias Flags.2 : Reset Connected
Last4 Alias Flags.3 : Reset Last4

Dim Knocksequence(maxknocks) As Byte
Dim Rx_sequence(maxknocks) As Byte
Dim Testsequence(6)as Byte
Rx_function Alias Rx_array(1)


Declare Sub Knock(num_of_knocks As Byte , Sequence As Byte)
Declare Sub Unicast(targ_id As Byte)
Declare Function Compare(num_of_pauses As Byte , Sequence1 As Byte , Sequence2 As Byte)as Byte


Ddrc.3 = 1 : Portc.3 = 0                                    'set outputpin for selenoid

'enable interrupt if data is received on uart
On Urxc Rx_in
Enable Urxc

Config Adc = Single , Prescaler = Auto , Reference = Internal
Start Adc

'enable timer-interrupt
Config Timer0 = Timer , Prescale = 256
Tcnt0 = 12
On Timer0 Irq_millis
Enable Timer0

Enable Interrupts

'read own id from xbee (my)
Print "+++";
Waitms 200
Rx_string = ""
Print "atmy" ; Chr(13)
Waitms 200
Rx_string = Left(rx_string , 1)
My = Val(rx_string)
Print "atcn" ; Chr(13)
Rx_string = ""
Reset Rx_received
Wait 5
For I = 1 To My
   Knocksequence(i) = 20
Next
Knock My , Knocksequence(1)

'****************** MAIN ***************************************

Do
Knockval = Getadc(0)
If Knockval > Threshold Then                                'got a knock
   If Secs2 > 0 Then                                        'on direct connection
      Print "t 0 1" ; Chr(13)                               'send immediately
      Set Printed
      Secs2 = 10                                            'keep direct connected
      Goto Loopend
   End If
   If Secs > 0 Then                                         'i am recording
      Gosub Nextknock
   Else
      Starttime = Millis                                    'seems to be a new sequence
      Knockcount = 1
      For I = 1 To Maxknocks
         Knocksequence(i) = 0
      Next I
      Reset Printed
      Secs3 = Wakeup_time
   End If
   Waitms Debouncetime
   Secs = 2
End If

If Secs3 = 0 Then
   Tmp = 5 : Testsequence(1) = 40 : Testsequence(2) = 40 : Testsequence(3) = 20 : Testsequence(4) = 20
   Knock Tmp , Testsequence(1)
End If


If Printed = 0 And Secs = 0 And Secs1 = 0 And Secs2 = 0 And Knockcount > 1 Then       'broadcast the first sequence
   Disable Urxc
   Gosub Broadcast
   Print "k " ; My ; " ";
   For I = 2 To Knockcount
      Print Knocksequence(i -1) ; " ";
   Next I
   Print Chr(13);
   If Knockcount = 7 Then
      Gosub Chk_secret
   End If
   Knock Knockcount , Knocksequence(1)                      'local echo
   Tmp = 0
   Unicast Tmp
   Set Printed
   Enable Urxc
End If

If Printed = 0 And Secs = 0 And Secs1 > 0 And Secs2 = 0 And Knockcount > 1 Then       'checking the answer-sequence
   Tmp = 2 : Testsequence(1) = 100                          'set fault sequence first - overwriten if sequences fit
   Decr Rx_count
   If Rx_count = Knockcount Then
       Checkright = Compare(knockcount , Rx_sequence(1) , Knocksequence(1))
       If Checkright = Knockcount Then
         Tmp = 3 : Testsequence(1) = 18 : Testsequence(2) = 18
         Secs2 = 20
         Unicast Rx_sender
         Print "c " ; My ; " 20" ; Chr(13)
      End If
   End If
   Knock Tmp , Testsequence(1)
   Set Printed
   Tmp = 0
   Secs1 = 0
End If


If Rx_received = 1 Then
   'Print Rx_string
   Gosub Analyse_rx
   Rx_string = ""
   Reset Rx_received
End If

Loopend:
Loop
End


'*********************** SUBS / FUNCTIONS **********************************

Nextknock:
If Knockcount < Maxknocks Then
   Now = Millis
   Knockdelay = Now - Starttime
   Knocksequence(knockcount) = Knockdelay
   Incr Knockcount
   Starttime = Now
End If
Return


Analyse_rx:
'most remote messages look like:
'command blank id_of_sender blank pause_between_1st_and_2nd_knock blank pause_between_2nd_and_3rd_knock blank ... carriage_return
'example: k 4 40 40 20 20[CR]   -> device 4 wants me to knock a sequence of 5 knocks (4 pauses)

If Left(rx_string , 1) = "t" Then                           'remote device wants me to knock a single "t"ick
   Rx_count = 1 : Rx_sequence(1) = 0
   Knock Rx_count , Rx_sequence(1)
   Secs2 = 10
   Goto Getout
End If

Rx_length = Split(rx_string , Rx_array(1) , " ")
Rx_sender = Val(rx_array(2))
For I = 3 To Rx_length
   Rx_sequence(i -2) = Val(rx_array(i))
Next I
'Print Rx_function ; "," ; Rx_sender ; "," ; Rx_sequence(1) ; "," ; Rx_sequence(2)
Rx_count = Rx_length - 1

If Rx_function = "k" And Secs2 = 0 Then                     'remote device wants me to "k"nock a sequence
   Knock Rx_count , Rx_sequence(1)
   Secs1 = 10
End If

If Rx_function = "c" Then                                   'when remote device successfully verified my sequence, it sends this "c"onnected string
   Tmp = 3 : Testsequence(1) = 18 : Testsequence(2) = 18
   Knock Tmp , Testsequence(1)
   Unicast Rx_sender
   Secs1 = 0
   Secs2 = 10
End If

Getout:
Return


Function Compare(num_of_pauses As Byte , Sequence1 As Byte , Sequence2 As Byte)as Byte
Local Count_fits As Byte
Count_fits = 0
For I = 1 To Num_of_pauses
   Abstol = Sequence1(i) * Tolerance
   Abstol = Abstol / 100
   Tolmax = Sequence1(i) + Abstol
   Tolmin = Sequence1(i) - Abstol
   If Sequence2(i) <= Tolmax And Tolmin <= Sequence2(i) Then Count_fits = Count_fits + 1
Next I
Compare = Count_fits
End Function


Sub Knock(num_of_knocks As Byte , Sequence As Byte)
Local Pausetime As Word
For I = 1 To Num_of_knocks
'Print I ; " " ; sequence(i) ; Chr(13)
   Pausetime = Sequence(i) * 8
   Portc.3 = 1
   Waitms Ontime
   Portc.3 = 0
   If Pausetime >= Ontime Then
      Waittime = Pausetime - Ontime
   Else
      Waittime = 1
   End If
   Waitms Waittime
Next I
Waitms 200                                                  'prevents echoing my own knock
Secs3 = Wakeup_time
End Sub

Chk_secret:
Testsequence(1) = 50 : Testsequence(2) = 25 : Testsequence(3) = 25 : Testsequence(4) = 50
Testsequence(5) = 100 : Testsequence(6) = 50
tmp = 6
Checkright = Compare(tmp , Knocksequence(1) , Testsequence(1))
If Checkright = 6 Then
   Gosub Knockeeprom
End If
Return


Broadcast:
Print "+++";
Waitms 200
Print "atdlffff,cn" ; Chr(13);
Waitms 200
Rx_string = ""
Reset Rx_received
Return

Sub Unicast(targ_id As Byte)
Print "+++";
Waitms 200
Print "atdl" ; Targ_id ; ",cn" ; Chr(13);
Waitms 200
Rx_string = ""
Reset Rx_received
End Sub

Irq_millis:
Incr Millis
Incr Msecs
If Msecs = 128 Then
   Msecs = 0
   If Secs > 0 Then
      Decr Secs
   End If
   If Secs1 > 0 Then
      Decr Secs1
   End If
   If Secs2 > 0 Then
      Decr Secs2
   End If
   If Secs3 > 0 Then
      Decr Secs3
   End If
End If
Tcnt0 = 12
Return


Rx_in:
Rx_byte = Inkey()
If Rx_byte <> 13 Then
   Rx_string = Rx_string + Chr(rx_byte)
Else
   Set Rx_received
End If
Return

Knockeeprom:
For I = 0 To Easterlength
   Codestring = ""
   Readeeprom Letter , I
   If Asc(letter) > 122 Or Asc(letter) < 32 Then Goto Weiter
   If Asc(letter) = 107 Then Letter = "c"                   'k durch c ersetzen
'   Print Left(letter , 1) ; " " ;                           'Asc(letter) ; " ";
   If Asc(letter) = 32 Then
      Codestring = Codestring + "4"                         'leerzeichen gibt eine 4-fache Pause (Wortende)
      Set Last4                                             'dies merken, damit beim naechsten letter KEINE 3 kommt
   Else
      If I > 0 And Last4 = 0 Then Codestring = Codestring + "3"       'normalerweise jeden neuen Buchstaben mit einer 3er-Pause einleiten
      Asci = Asc(letter) - 96                               'aus ASCII die Buchstabennummer (1-25) machen
      If Asci > 10 Then Decr Asci                           'ab dem "k" alles ascis um 1 nach unten verschieben - also "k" ingnorieren
      Decr Asci                                             '!! damit 5/5=0 ergibt und (5 mod 5)=5 ergibt !!
      Nummer = Asci / 5                                     'den ganzzahligen Anteil ermitteln
      For J = 1 To Nummer
         Codestring = Codestring + "1"                      'an den Zeilenteil rekursiv immer eine 1 anfuegen
      Next J
      Codestring = Codestring + "2"                         'zwischen Zeile und Spalte eine doppelte Pause einfuegen
      Nummer = Asci Mod 5                                   'den Rest ermitteln
      For J = 1 To Nummer
         Codestring = Codestring + "1"                      'an den Spaltenteil rekursiv immer eine 1 anfuegen
      Next J
      Reset Last4
   End If

'   Print Codestring

   Knock_string_length = Len(codestring)
   Portc.3 = 1                                              'der erste Klopf
   Waitms Ontime
   Portc.3 = 0
   For J = 1 To Knock_string_length
      Letter = Mid(codestring , J , 1)
      Pause = Val(letter) * Pausen_faktor
      Waitms Pause                                          'die ermittelte Pause
      Portc.3 = 1                                           'der abschliessende Klopf
      Waitms Ontime
      Portc.3 = 0
   Next J
  Weiter:
Next I
Wait 2

Return


'**************** Data *****************
Dta:
$eeprom
$eepromhex
Data "tap code generator of arne groh"
$data

End